# Copyright (C) 2012 by Sean Sherrard.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Public Key Cryptography Standards

This module provides methods for working with public key cryptography blah

PKCS #5 v2.1 (RFC 2898) "Password-Based Cryptography"
PKCS #7 v1.5 (RFC 2315) "Cryptographic Message Syntax"
PKCS #8 v1.2 (RFC 5208) "Private-Key Information Syntax"
PKCS #12 v1.0 (no RFC?) "Personal Information Exchange Syntax"
X.509 v3: (RFC 5280, RFC 3279) "PKIX Certificate and CRL Profile"
"""
__author__ = 'Sean Sherrard'
__version__ = '1.1'
__copyright__ = "Copyright 2012, Sean Sherrard"

from binascii import hexlify, unhexlify
import asn1
import Crypto
from Crypto.Util.strxor import strxor
from Crypto.Util.number import inverse

OID_RSADSI = asn1.OID('1.2.840.113549')  # RSA Data Security Inc.
OID_X957 =   asn1.OID('1.2.840.10040')  # ANSI standard X9.57 (1997): "Public Key Cryptography for the Financial Services Industry: Certificate Management"
OID_SECSIG = asn1.OID('1.3.14.3')  # Open Systems Implementors Workshop (OIW) Security Special Interest Group
OID_CSOR =   asn1.OID('2.16.840.1.101.3')  # Computer Security Objects Registry (CSOR)
OID_DSAT =   asn1.OID('2.5.4')  # OSI Directory Services Attribute type (X.501)
OID_DSCX =   asn1.OID('2.5.29')  # OSI Directory Services Certificate Extension (X.501)

OID_PKCS1 = asn1.OID(OID_RSADSI, '1.1')
OID_PKCS3 = asn1.OID(OID_RSADSI, '1.3')
OID_PKCS5 = asn1.OID(OID_RSADSI, '1.5')
OID_PKCS7 = asn1.OID(OID_RSADSI, '1.7')
OID_PKCS9 = asn1.OID(OID_RSADSI, '1.9')
OID_PKCS12 = asn1.OID(OID_RSADSI, '1.12')

OID_PKCS_RSAPKEY = asn1.OID(OID_PKCS1, '1')
OID_PKCS_DSAPKEY = asn1.OID(OID_X957, '4.1')
OID_PKCS_DHPKEY = asn1.OID(OID_PKCS3, '1')
OID_PKCS_DHPUBNUM = asn1.OID('1.2.840.10046.2.1')
OID_PKCS_PBKDF2 = asn1.OID(OID_PKCS5, '12')
OID_PKCS_PBKDF2_PRF = asn1.OID(OID_RSADSI, '2.7')
OID_PKCS_X590CERT = asn1.OID(OID_PKCS9, '22.1')
OID_PKCS_SDSICERT = asn1.OID(OID_PKCS9, '22.2')

OID_PCKS7_DATA = asn1.OID(OID_PKCS7, '1')
OID_PCKS7_SIGNED_DATA = asn1.OID(OID_PKCS7, '2')
OID_PCKS7_ENVELOPED_DATA = asn1.OID(OID_PKCS7, '3')
OID_PCKS7_SIGNED_ENVELOPED_DATA = asn1.OID(OID_PKCS7, '4')
OID_PCKS7_DIGESTED_DATA = asn1.OID(OID_PKCS7, '5')
OID_PCKS7_ENCRYPTED_DATA = asn1.OID(OID_PKCS7, '6')

OID_PCKS12_KEYBAG = asn1.OID(OID_PKCS12, '10.1.1')
OID_PCKS12_SHROUDED_KEYBAG = asn1.OID(OID_PKCS12, '10.1.2')
OID_PCKS12_CERTBAG = asn1.OID(OID_PKCS12, '10.1.3')
OID_PCKS12_CRLBAG = asn1.OID(OID_PKCS12, '10.1.4')
OID_PCKS12_SECRETBAG = asn1.OID(OID_PKCS12, '10.1.5')
OID_PCKS12_SAFECONTENTS = asn1.OID(OID_PKCS12, '10.1.6')

PBES_PARAMS = {asn1.OID(OID_PKCS5, '1'):    ('PBES1', 'MD2', 64,   'DES',  'CBC', 'RFC1423'), # pbeWithMD2AndDES-CBC
			   asn1.OID(OID_PKCS5, '3'):    ('PBES1', 'MD5', 64,   'DES',  'CBC', 'RFC1423'), # pbeWithMD5AndDES-CBC
			   asn1.OID(OID_PKCS5, '4'):    ('PBES1', 'MD2', 64,   'ARC2', 'CBC', 'RFC1423'), # pbeWithMD2AndRC2-CBC
			   asn1.OID(OID_PKCS5, '6'):    ('PBES1', 'MD5', 64,   'ARC2', 'CBC', 'RFC1423'), # pbeWithMD5AndRC2-CBC
			   asn1.OID(OID_PKCS5, '9'):    ('PBES1', 'MD5', 64,   'XOR',   None, 'RFC1423'), # pbeWithMD5AndXOR
			   asn1.OID(OID_PKCS5, '10'):   ('PBES1', 'SHA', 64,   'DES',  'CBC', 'RFC1423'), # pbeWithSHA1AndDES-CBC
			   asn1.OID(OID_PKCS5, '11'):   ('PBES1', 'SHA', 64,   'ARC2', 'CBC', 'RFC1423'), # pbeWithSHA1AndRC2-CBC
			   asn1.OID(OID_PKCS5, '13'):   ('PBES2',  None, None,  None,   None, None),
			   asn1.OID(OID_PKCS12, '1.1'): ('PBES3', 'SHA', 128,  'ARC4',  None, 'RFC1423'), # pbeWithSHAAnd128BitRC4
			   asn1.OID(OID_PKCS12, '1.2'): ('PBES3', 'SHA', 40,   'ARC4',  None, 'RFC1423'), # pbeWithSHAAnd40BitRC4
			   asn1.OID(OID_PKCS12, '1.3'): ('PBES3', 'SHA', 192,  'DES3', 'CBC', 'RFC1423'), # pbeWithSHAAnd3-KeyTripleDES-CBC
			   asn1.OID(OID_PKCS12, '1.4'): ('PBES3', 'SHA', 128,  'DES3', 'CBC', 'RFC1423'), # pbeWithSHAAnd2-KeyTripleDES-CBC
			   asn1.OID(OID_PKCS12, '1.5'): ('PBES3', 'SHA', 128,  'ARC2', 'CBC', 'RFC1423'), # pbeWithSHAAnd128BitRC2-CBC
			   asn1.OID(OID_PKCS12, '1.6'): ('PBES3', 'SHA', 40,   'ARC2', 'CBC', 'RFC1423')} # pbewithSHAAnd40BitRC2-CBC

SIGN_PARAMS = {asn1.OID(OID_PKCS1, '2'):   ('RSA', 'MD2'),  # md2WithRSAEncryption
			   asn1.OID(OID_PKCS1, '4'):   ('RSA', 'MD5'),  # md5WithRSAEncryption
			   asn1.OID(OID_PKCS1, '5'):   ('RSA', 'MD5'),  # sha-1WithRSAEncryption
			   asn1.OID(OID_X957,  '4.3'): ('DSA', 'SHA'),  # id-dsa-with-sha1
			   }

DIGEST_PARAMS = {asn1.OID(OID_RSADSI, '2.2'):  (None,   'MD2'),
				 asn1.OID(OID_RSADSI, '2.5'):  (None,   'MD5'),
				 asn1.OID(OID_SECSIG, '2.26'): (None,   'SHA'),
				 asn1.OID(OID_RSADSI, '2.7'):  ('HMAC', 'SHA'),    # hmacWithSHA1
				 asn1.OID(OID_RSADSI, '2.8'):  ('HMAC', 'SHA224'), # hmacWithSHA224
				 asn1.OID(OID_RSADSI, '2.9'):  ('HMAC', 'SHA256'), # hmacWithSHA256
				 asn1.OID(OID_RSADSI, '2.10'): ('HMAC', 'SHA384'), # hmacWithSHA384
				 asn1.OID(OID_RSADSI, '2.11'): ('HMAC', 'SHA512')} # hmacWithSHA512 

CIPHER_PARAMS = {asn1.OID(OID_SECSIG, '2.7'):  (64,   'DES',  'CBC', 'RFC1423'), # DES-CBC-Pad
				 asn1.OID(OID_RSADSI, '3.7'):  (192,  'DES3', 'CBC', 'RFC1423'), # DES-EDE3-CBC-Pad
				 asn1.OID(OID_RSADSI, '3.2'):  (None, 'ARC2', 'CBC', 'RFC1423'), # RC2-CBC-Pad
				 asn1.OID(OID_RSADSI, '3.3'):  (None, 'ARC2', 'ECB', None),
#				 asn1.OID(OID_RSADSI, '3.4'):  (None, 'ARC4',  None, None),
#				 asn1.OID(OID_RSADSI, '3.8'):  (None, 'ARC5',  None, None),    # RC5-CBC
#				 asn1.OID(OID_RSADSI, '3.9'):  (None, 'ARC5',  None, 'RFC1423'), # RC5-CBC-Pad
				 asn1.OID(OID_CSOR, '4.1.1'):  (128,  'AES',  'ECB', 'RFC1423'), # AES128-ECB-Pad
				 asn1.OID(OID_CSOR, '4.1.2'):  (128,  'AES',  'CBC', 'RFC1423'), # AES128-CBC-Pad
				 asn1.OID(OID_CSOR, '4.1.3'):  (128,  'AES',  'OFB', 'RFC1423'), # AES128-OFB-Pad
				 asn1.OID(OID_CSOR, '4.1.4'):  (128,  'AES',  'CFB', 'RFC1423'), # AES128-CFB-Pad
				 asn1.OID(OID_CSOR, '4.1.21'): (192,  'AES',  'ECB', 'RFC1423'), # AES192-ECB-Pad
				 asn1.OID(OID_CSOR, '4.1.22'): (192,  'AES',  'CBC', 'RFC1423'), # AES192-CBC-Pad
				 asn1.OID(OID_CSOR, '4.1.23'): (192,  'AES',  'OFB', 'RFC1423'), # AES192-OFB-Pad
				 asn1.OID(OID_CSOR, '4.1.24'): (192,  'AES',  'CFB', 'RFC1423'), # AES192-CFB-Pad
				 asn1.OID(OID_CSOR, '4.1.41'): (256,  'AES',  'ECB', 'RFC1423'), # AES256-ECB-Pad
				 asn1.OID(OID_CSOR, '4.1.42'): (256,  'AES',  'CBC', 'RFC1423'), # AES256-CBC-Pad
				 asn1.OID(OID_CSOR, '4.1.43'): (256,  'AES',  'OFB', 'RFC1423'), # AES256-OFB-Pad
				 asn1.OID(OID_CSOR, '4.1.44'): (256,  'AES',  'CFB', 'RFC1423')} # AES256-CFB-Pad

ATTR_NAMES = {asn1.OID(OID_PKCS9, '1'):  'emailAddress',
			  asn1.OID(OID_PKCS9, '20'): 'friendlyName',
			  asn1.OID(OID_PKCS9, '21'): 'localKeyId',
			  asn1.OID(OID_DSAT, '3'):   'cn', # commonName
			  asn1.OID(OID_DSAT, '4'):   'sn', # surname
			  asn1.OID(OID_DSAT, '6'):   'c',  # countryName
			  asn1.OID(OID_DSAT, '10'):  'o',  # organizationName
			  asn1.OID(OID_DSAT, '11'):  'ou', # organizationalUnitName
			  asn1.OID(OID_DSAT, '41'):  'name',
			  asn1.OID(OID_DSAT, '42'):  'givenName',
			  asn1.OID(OID_DSCX, '14'):  'subjectKeyIdentifier',
			  asn1.OID(OID_DSCX, '15'):  'keyUsage',
			  asn1.OID(OID_DSCX, '17'):  'subjectAltName',
			  asn1.OID(OID_DSCX, '19'):  'basicConstraints',
			  asn1.OID(OID_DSCX, '30'):  'nameConstraints',
			  asn1.OID(OID_DSCX, '32'):  'certificatePolicies',
			  asn1.OID(OID_DSCX, '33'):  'policyMappings',
			  asn1.OID(OID_DSCX, '35'):  'authorityKeyIdentifier',
			  asn1.OID(OID_DSCX, '36'):  'policyConstraints',
			  asn1.OID(OID_DSCX, '37'):  'extKeyUsage'}

class PKCSError(Exception):
	pass

def btol(val, nbytes=None):
	if nbytes is None:
		return int(hexlify(val), 16)
	else:
		return int(hexlify(val[-nbytes:]), 16)

def ltob(val, nbytes=None):
	hexval = hex(val)[2:-1] if type(val) is long else hex(val)[2:]
	if nbytes is None:
		return unhexlify('0'*(len(hexval)&1) + hexval)
	else:
		return unhexlify('0'*(nbytes*2-len(hexval)) + hexval[-nbytes*2:])

def	ceildiv(n, d):
	return (n+d-1)//d

def make_pkey(keyAlg, keyData, isPublic):
	algoId, algoParams = keyAlg.get_slice(0,2)
	if algoId == OID_PKCS_RSAKEY:
		from Crypto.PublicKey import RSA
		vals = asn1.loads(keyData)
		if isPublic:
			vals = vals[0:2] # n, e
		else:
			vals = vals[1:6] # n, e, d, p, q
		key = RSA.construct([long(val) for val in vals])
	elif algoId == OID_X957_DSAKEY:
		from Crypto.PublicKey import DSA
		p, q, g = algoParams
		val = asn1.loads(keyData)
		if isPublic:
			vals = (val, g, p, q) # y, g, p, q
		else:
			y = pow(g, val, p)
			vals = (y, g, p, q, val) # y, g, p, q, x
		key = DSA.construct([long(val) for val in vals])
	else:
		raise ValueError('unknown key algorithm id: {}'.format(algoId))
#	if attrs:
#		for attrId, attrValues in attrs:
#			if attrId in ATTR_NAMES and attrValues:
#				setattr(key, ATTR_NAMES[attrId], attrValues[0])
	return key

def make_certkey(certId, certValue):
	if certId == OID_PKCS_X590CERT:
		cert = asn1.loads(certValue)
		pass
	elif certId == OID_PKCS_SDSICERT:
		cert = b64decode(certValue.value)
		print "========  SDSI CERT  =============="
		asn1.pr(cert)
		print "==================================="
	else:
		raise ValueError('unknown certificate type id: {}'.format(certId))
	return key

def load_x509(cert):
	def make_attrlist(value):
		return [{ATTR_NAMES.get(id, id): val} for item in value for id, val in item.iteritems()]
	cert, signAlg, signData = cert
	idx = cert.find_tag(0)
	pkeyAlgo, pkeyData = cert[idx+6]
	algoId, algoParms = pkeyAlgo.get_pos(0,2)
	if algoId == OID_PKCS_RSAPKEY:
		from Crypto.PublicKey import RSA
		rsakey = asn1.loads(pkeyData)
		vals = rsakey[0:2] # n, e
		pkey = RSA.construct([long(val) for val in rsakey])
	elif algoId == OID_PKCS_DSAPKEY:
		from Crypto.PublicKey import DSA
		p, q, g = algoParms
		y = asn1.loads(pkeyData)
		vals = (y, g, p, q)
		pkey = DSA.construct([long(val) for val in vals])
	elif algoId == OID_PKCS_DHPUBNUM:
		from Crypto.PublicKey import ElGamal
		p, g, q = algoParms[0:3]
		y = asn1.loads(pkeyData)
		vals = (p, g, y)
		pkey = ElGamal.construct([long(val) for val in vals])
	else:
		raise PKCSError('unknown X.509 key algorithm: {}'.format(algoId))
	pkey.serialNumber = cert[idx+1]
	pkey.signature = cert[idx+2]
	pkey.issuer = make_attrlist(cert[idx+3])
	pkey.validFrom, pkey.validTo = cert[idx+4]
	pkey.subject = make_attrlist(cert[idx+5])
	pkey.version = cert.get_tag(0, None, 0) + 1
	if pkey.version >= 2:
		pkey.issuerUniqueID = cert.get_tag(1, asn1.BIT_STRING)
		pkey.subjectUniqueID = cert.get_tag(2, asn1.BIT_STRING)
	if pkey.version >= 3:
#		pkey.extensions = cert.get_tag(3, None, [])
		extensions = cert.get_tag(3, None, [])
		pkey.extensions = {}
		for extension in extensions:
			extnId = extension[0]
			if extension[1] == True:
				extnVal = (True, asn1.loads(extension[2]))
			else:
				extnVal = (False, asn1.loads(extension[1]))
			pkey.extensions[ATTR_NAMES.get(extnId, extnId)] = extnVal
	return pkey

def load_pkcs8(pkeyInfo, passwd=None):

	def load_unencrypted(pkeyInfo):
		version, pkeyAlgo, pkeyData = pkeyInfo[0:3]
		if version != 0:
			raise PKCSError('unknown PKCS#8 version: {}'.format(version))
		algoId, algoParms = pkeyAlgo.get_pos(0,2)
		if algoId == OID_PKCS_RSAPKEY:
			from Crypto.PublicKey import RSA
			rsakey = asn1.loads(pkeyData)
			vals = rsakey[1:6] # n, e, d, p, q
			pkey = RSA.construct([long(val) for val in vals])
			print 'RSA!', vals
		elif algoId == OID_PKCS_DSAPKEY:
			from Crypto.PublicKey import DSA
			p, q, g = algoParms
			x = asn1.loads(pkeyData)
			y = pow(g, x, p)
			vals = (y, g, p, q, x)
			pkey = DSA.construct([long(val) for val in vals])
		elif algoId == OID_PKCS_DHPKEY:
			from Crypto.PublicKey import ElGamal
			p, g = algoParms[0:2]
			x = asn1.loads(pkeyData)
			y = pow(g, x, p)
			vals = (p, g, y, x)
			pkey = ElGamal.construct([long(val) for val in vals])
		else:
			raise PKCSError('unknown PKCS#8 key algorithm: {}'.format(algoId))
		return pkey

	def load_encrypted(pkeyInfo, passwd):
		encrAlgo, encrData = pkeyInfo[0:2]
		cipher = _pbecipher_fromAlgoInfo(passwd, encrAlgo)
		pkeyData = cipher.decrypt(encrData)
		pkeyInfo = asn1.loads(pkeyData)
		return load_unencrypted(pkeyInfo)

	if isinstance(pkeyInfo[0], int):
		return load_unencrypted(pkeyInfo)
	elif isinstance(pkeyInfo[0], list):
		return load_encrypted(pkeyInfo, passwd)
	else:
		raise PKCSError('invalid PKCS#8 structure')

def dump_pkcs8(pkey, cipher=None):

	def dump_unencrypted(pkey):
		keytype = pkey.__module__.rsplit('.',1)[-1]
		if keytype == 'RSA':
			algoId = OID_PKCS_RSAPKEY
			algoParms = None
			vals = [0, pkey.n, pkey.e, pkey.d, pkey.p, pkey.q,
					pkey.d % (pkey.p - 1), pkey.d % (pkey.q - 1),
					inverse(pkey.q, pkey.p)]
			pkeyData = asn1.dumps(vals)
		elif keytype == 'DSA':
			algoId = OID_PKCS_DSAPKEY
			algoParms = (pkey.p, pkey.q, pkey.g)
			pkeyData = asn1.dumps(pkey.x)
		elif keytype == 'ElGamal':
			algoId = OID_PKCS_DHPKEY
			algoParms = (pkey.p, pkey.g)
			pkeyData = asn1.dumps(pkey.x)
		else:
			raise TypeError('unknown private key implementation')
		pkeyAlgo = (algoId, algoParms)
		return (0, pkeyAlgo, pkeyData)

	def dump_encrypted(pkey, cipher):
		pkeyInfo = dump_unencrypted(pkey)
		pkeyData = asn1.dumps(pkeyInfo)
		encrAlgo = _pbecipher_toAlgoInfo(cipher)
		encrData = cipher.encrypt(pkeyData)
		return (encrAlgo, encrData)

	if cipher:
		return dump_encrypted(pkey, cipher)
	else:
		return dump_unencrypted(pkey)

def _pbecipher_fromAlgoInfo(passwd, algoInfo):
	algoId, algoParms = algoInfo
	try:
		pbes, hash, keybits, cipher, mode, pad = PBES_PARAMS[algoId]
	except KeyError:
		raise PKCSError('unknown PBES id: {}'.format(algoId))
	print pbes, hash, keybits, cipher, mode, pad
	cipher_args = {}
	if pbes == 'PBES1':
		kdf = PBKDF1
		salt, count = algoParms
	elif pbes == 'PBES2':
		kdfAlgo, ciphAlgo = algoParms
		kdfId, kdfParms = kdfAlgo
		if kdfId == OID_PKCS_PBKDF2:
			kdf = PBKDF2
			salt, count, dklen, prfId = kdfParms.get_pos(0, 4)
			prfId = prfId or OID_PKCS_PBKDF2_PRF
			try:
				hmac, hash = DIGEST_PARAMS[prfId]
			except KeyError:
				raise PKCSError('unknown digest id: {}'.format(prfId))
		else:
			raise PKCSError('unknown KDF id: {}'.format(kdfId))
		ciphId, ciphParms = ciphAlgo
		try:
			keybits, cipher, mode, pad = CIPHER_PARAMS[ciphId]
		except KeyError:
			raise PKCSError('unknown cipher id: {}'.format(prfId))
		if cipher == 'ARC2':
			keybits, ivec = ciphParms
			keybits = {160:40, 120:64, 58:128}.get(keybits, keybits)
		else:  # DES, DES3, AES
			ivec = ciphParms
		cipher_args['IV'] = ivec
	elif pbes == 'PBES3':
		kdf = PBKDF3
		salt, count = algoParms
		passwd = (unicode(passwd) + u'\0').encode('utf-16-be') if passwd is not None else b''
	else:
		raise RuntimeError('invalid PBES id: {}'.format(pbes))
	try:
		hash = getattr(__import__('Crypto.Hash', fromlist=[hash]), hash)
		cipher = getattr(__import__('Crypto.Cipher', fromlist=[cipher]), cipher)
		pad = pad and globals()[pad]
	except (AttributeError, KeyError):
		raise RuntimeError('invalid PBES module: {} {} {}'.format(hash, cipher, pad)) 
	print passwd, kdf, repr(salt), count, keybits, mode, cipher_args
	return PBECipher(passwd, cipher, kdf, hash, salt, count, 
					 keybits, mode, pad, **cipher_args)

def _pbecipher_toAlgoInfo(cipher):
	try:
		kdf, hash, salt, count, keybits, mode, cipher, pad = (
				cipher.kdf, cipher.hash, cipher.salt, cipher.count,
				cipher.keybits, cipher.mode, cipher.cipher, cipher.pad)
		pbes = {PBKDF1: 'PBES1', PBKDF2: 'PBES2', PBKDF3: 'PBES3'}[kdf]
	except (AttributeError, KeyError):
		raise TypeError('invalid PBE Cipher: {}'.format(cipher))
	hash = hash.__name__.rsplit('.',1)[-1]
	cipher = type(cipher).__name__
	pad = pad and pad.__name__
	def find_parmid(param_map, param_set):
		for parmid, parmval in param_map.iteritems():
			if parmval == param_set:
				return parmid
		raise PKCSError('unknown PBES parameter set: {}'.format(param_set))
	if pbes == 'PBES1':
		algoId = find_parmid(PBES_PARAMS, (pbes, hash, keybits, cipher, mode, pad))
		algoParms = (salt, count)
	elif pbes == 'PBES2':
		algoId = find_parmid(PBES_PARAMS, (pbes, None, None, None, None, None))
		if kdf is PBKDF2:
			kdfId = OID_PKCS_PBKDF2
			prfId = find_parmid(DIGEST_PARAMS, ('HMAC', hash))
			if prfId == OID_PKCS_PBKDF2_PRF:
				kdfParms = (salt, count)
			else:
				kdfParms = (salt, count, keybits//8, prfId)
		else:
			raise PKCSError('unknown KDF id: {}'.format(kdf))
		kdfAlgo = (kdfId, kdfParms)
		ciphId = find_parmid(CIPHER_PARAMS, (keybits, cipher, mode, pad))
		if cipher == 'ARC2':
			keybits = {40:160, 64:120, 128:58}.get(keybits, keybits)
			ciphParms = (keybits, cipher.ivec)
		else:  # DES, DES3, AES
			ciphParms = cipher.ivec
		ciphAlgo = (ciphId, ciphParms)
		algoParms = (kdfAlgo, ciphAlgo)
	elif pbes == 'PBES3':
		algoId = find_parmid(PBES_PARAMS, (pbes, hash, keybits, cipher, mode, pad))
		algoParms = (salt, count)
	else:
		raise RuntimeError('invalid PBES id: {}'.format(pbes))
	algoInfo = (algoId, algoParms)
	return algoInfo

def PBKDF1(P, S, count, dklen, ivlen, hash):
	if dklen+ivlen > hash.digest_size:
		raise PKCSError('requested key length is too long for ' + hash.__name__)
	T = reduce(lambda Ti,_: hash.new(Ti).digest(), xrange(count), P+S)
	return T[:dklen], T[dklen:dklen+ivlen]

def PBKDF2(P, S, count, dklen, ivlen, hash, hmac=None):
	if hmac is None:
		import Crypto.Hash.HMAC as hmac
	print repr(P), repr(S), count, dklen, ivlen, hash, hmac
	u = hash.digest_size
	l = ceildiv(dklen,u)
	def U(Uf, Ui):
		Ui = hmac.new(P, Ui, hash).digest()
		Uf = strxor(Uf, Ui)
		return Uf, Ui
	Uo = ltob(0, u)
	print 'u', u, 'l', l, 'Uo', repr(Uo)
	T = [reduce(lambda (Uf,Ui),_: U(Uf,Ui), xrange(count), (Uo, S+ltob(i,4)))[0]
			for i in xrange(1, l+1)]
	print 'T', repr(T)
	return b''.join(T)[:dklen], None

def PBKDF3(P, S, count, dklen, ivlen, hash):
	def makelen(bytes, tolen):
		q, r = divmod(tolen, len(bytes)) if bytes else (0, 0)
		return bytes*q + bytes[:r]
	u = hash.digest_size
	v = hash.block_size
	S = makelen(S, v*ceildiv(len(S), v))
	P = makelen(P, v*ceildiv(len(P), v))
	I = S+P
	def kdf(xlen, id):
		k = ceildiv(xlen, u)
		D = chr(id)*v
		A = []
		for i in xrange(1, k+1):
			Ai = reduce(lambda Ai,_: hash.new(Ai).digest(), xrange(count), D+I)
			A.append(Ai)
			if i == k: break
			B = btol(makelen(Ai, v)) + 1
			I = ''.join([ltob(btol(I[j:j+v]) + B, v) for j in range(0, len(I), v)])
		return b''.join(A)[:xlen]
	key = kdf(dklen, 1)
	init = kdf(ivlen, 2) if ivlen > 1 else None
	return key, init

def RFC1423(pbecipher):
	def iterencrypt(data, last=False):
		block_size = pbecipher.block_size
		data = pbecipher._enctail + data
		if last:
			pad = block_size - len(data) % block_size
			data = data + chr(pad) * pad
			assert len(data) % block_size == 0
		size = len(data) // block_size * block_size
		data, pbecipher._enctail = data[:size], data[size:]
		return pbecipher.cipher.encrypt(data)
	def iterdecrypt(data, last=False):
		block_size = pbecipher.block_size
		data = pbecipher._dectail + data
		size = len(data) // block_size * block_size
		data, pbecipher._dectail = data[:size], data[size:]
		data = pbecipher.cipher.decrypt(data)
		if last:
			pad = ord(data[-1])
			if not (1 <= pad <= block_size and 
					data[-pad:] == data[-1]*pad and
					not pbecipher._dectail):
				raise PKCSError('error decrypting data')
			data = data[:-pad]
		return data
	if pbecipher.block_size > 1:
		pbecipher._enctail = b''
		pbecipher._dectail = b''
		pbecipher.iterencrypt = iterencrypt
		pbecipher.iterdecrypt = iterdecrypt

class PBECipher(object):

	def __init__(self, passwd, cipher, kdf=PBKDF1, hash=None, salt=None,
				 count=None, keybits=None, mode=None, pad=RFC1423, **kwargs):
		if hash is None:
			import Crypto.Hash.SHA
		passwd = passwd or b''
		self.kdf = kdf
		self.hash = hash or Crypto.Hash.SHA  # default compatible with PyCrypto
		self.salt = salt or b''  # perhaps not the best default value
		self.count = count or 1000  # default compatible with PyCrypto
		self.keybits = keybits or cipher.key_size*8 or 128  # default compatible with PyCrypto
		self.mode = mode or 'CBC'
		self.pad = pad
		dklen = self.keybits // 8
		ivlen = cipher.block_size
		key, ivec = kdf(passwd, self.salt, self.count, dklen, ivlen, self.hash)
		if ivlen > 1:
			kwargs['mode'] = getattr(cipher, 'MODE_' + self.mode)
			self.ivec = kwargs.setdefault('IV', ivec)
		if cipher.__name__ == 'Crypto.Cipher.ARC2':
			kwargs.setdefault('effective_keylen', self.keybits)
		self.cipher = cipher.new(key, **kwargs)
		if pad:
			pad(self)

	def __getattr__(self, name):
		return getattr(self.cipher, name)

	def encrypt(self, data):
		return self.iterencrypt(data, True)

	def iterencrypt(self, data, last=False):
		return self.cipher.encrypt(data)

	def decrypt(self, data):
		return self.iterdecrypt(data, True)

	def iterdecrypt(self, data, last=False):
		return self.cipher.decrypt(data)

